
import * as fs from 'node:fs';
import path from 'path';
import Handlebars from "handlebars";
import ts, { PropertyAccessExpression, CallExpression, Decorator, ExpressionStatement, Identifier, MethodDeclaration, ObjectLiteralExpression, ParameterDeclaration, PropertyAssignment, StringLiteral, StringLiteralType } from 'typescript';
import { Json5Reader } from '@ohos/hvigor';
import { log } from './ModularityGeneratePlugin';
import { RouterConstant } from './ModuleConfigurator';

const ROUTER = "Router"

const ROUTER_GENERATE = "_generate"

export class RouterGenerate {

    static buildRouter(projectPath: string, modulePath: string, complete: (routeMapPath: string, componentFielPath: RouterConstant[]) => void, scanPackages?: string[], ignorePaths?: string[]) {
        const filePath = modulePath + "/src/main/ets"
        let fileList: string[] = []
        if (scanPackages && scanPackages.length > 0) {
            scanPackages.forEach(item => {
                fileList.push(...this.getFileByFilter(modulePath, filePath + item, ignorePaths))
            })
        } else {
            fileList.push(...this.getFileByFilter(modulePath, filePath, ignorePaths))
        }


        let analyzer: Analyzer
        let routerInfoList: RouterInfo[] = []
        let routerBuilderInfoList: RouterBuilderInfo[] = []
        let componentFileList: RouterConstant[] = []
        fileList.forEach((etsPath) => {
            //如果是自动生成的文件则忽略
            analyzer = new Analyzer(ROUTER, etsPath, modulePath, projectPath)
            //解析组件内容，获取自定义注解参数
            analyzer.start()
            if (analyzer.routerInfo && analyzer.routerBuilderInfo) {
                routerBuilderInfoList.push(analyzer.routerBuilderInfo)
                routerInfoList.push(analyzer.routerInfo)
                if (analyzer.nameNeedChange) {
                    componentFileList.push({
                        name: analyzer.routerInfo.name,
                        path: etsPath
                    })
                }
            }

        })

        const moduleJson5Path = modulePath + "/src/main/module.json5"
        const moduleJson5 = Json5Reader.getJson5Obj(moduleJson5Path, "utf8")
        const module = Json5Reader.getJson5ObjProp(moduleJson5, "module")
        const routerMapPath = Json5Reader.getJson5ObjProp(module, "routerMap")
        const routerMapDir = modulePath + "/src/main/resources/base/profile/"
        let routerMapJsonPath = ""
        if (!routerMapPath) {
            let routerMap = {
                routerMap: routerInfoList
            }
            if (!fs.existsSync(routerMapDir)) {
                fs.mkdirSync(routerMapDir, { recursive: true });
            }
            log("创建系统路由表" + routerMapDir + "router_map.json");
            fs.writeFileSync(routerMapDir + "router_map.json", JSON.stringify(routerMap, null, 2), { encoding: "utf8" })
            routerMapJsonPath = routerMapDir + "router_map.json"

            log("修改" + moduleJson5Path + "-添加routerMap属性$profile:router_map");
            moduleJson5.module.routerMap = "$profile:router_map"
            fs.writeFileSync(moduleJson5Path, JSON.stringify(moduleJson5, null, 2), { encoding: "utf8" })
        } else {
            const routerMapRealPath = (routerMapPath as string).replace("$profile:", routerMapDir) + ".json"
            routerMapJsonPath = routerMapRealPath
            try {
                let routerMapObj = JSON.parse(fs.readFileSync(routerMapRealPath, { encoding: "utf8" }))
                let filterList: RouterInfo[] = []
                routerInfoList.forEach(info => {
                    let originInfo = routerMapObj.routerMap.find((i: any) => i.name == info.name)
                    if (originInfo) {
                        originInfo.buildFunction = info.buildFunction
                        originInfo.pageSourceFile = info.pageSourceFile
                        if (info.data) {
                            originInfo.data = info.data
                        }
                    } else {
                        filterList.push(info)
                    }
                })
                routerMapObj.routerMap.push(...filterList)
                fs.writeFileSync(routerMapRealPath, JSON.stringify(routerMapObj, null, 2), { encoding: "utf8" })
                log("写入系统路由表")
            } catch (e) {
                let routerMap = {
                    routerMap: routerInfoList
                }
                fs.writeFileSync(routerMapRealPath, JSON.stringify(routerMap, null, 2), { encoding: "utf8" })
                log("写入系统路由表")
            }
        }

        this.generateRouterBuilderFile(routerBuilderInfoList, modulePath)
        complete(routerMapJsonPath, componentFileList)
    }

    static generateRouterBuilderFile(routerBuilderInfo: RouterBuilderInfo[], modulePath: string) {
        const builderPath = __dirname + "/router-builder.tpl";
        const tpl = fs.readFileSync(builderPath, { encoding: "utf8" });
        const template = Handlebars.compile(tpl);
        routerBuilderInfo.forEach(info => {
            const output = template({
                componentName: info.componentName,
                pageSourceFile: info.pageSourceFile,
                componentBuilder: info.componentBuilder
            });

            const routerBuilderDir = modulePath + "/src/main/ets" + `/${ROUTER_GENERATE}`;
            if (!fs.existsSync(routerBuilderDir)) {
                fs.mkdirSync(routerBuilderDir, { recursive: true });
            }
            fs.writeFileSync(`${routerBuilderDir}/${info.componentBuilder}.ets`, output, { encoding: "utf8" });
            log("创建路由builder-" + info.componentBuilder + ".ets")
        })
    }

    static getFileByFilter(modulePath: string, filePath: string, ignorePath: string[] | undefined): string[] {
        if (!filePath) {
            return []
        }
        // 忽略扫描的文件夹
        if (ignorePath && ignorePath.length > 0) {
            if (ignorePath.includes(filePath.replace(modulePath + "/src/main/ets", ""))) {
                return []
            }
        }
        // 过滤自动生成
        if (filePath.endsWith("src/main/ets/" + ROUTER_GENERATE)) {
            return []
        }

        const stats = fs.lstatSync(filePath)
        const fileArray: string[] = []
        if (stats.isFile()) {
            if (filePath.endsWith(".ets") || filePath.endsWith(".ETS")) {
                fileArray.push(filePath)
            }
            return fileArray
        } else if (stats.isDirectory()) {
            let dirName = path.basename(filePath)
            fs.readdirSync(filePath).forEach(file => {
                const fullPath = path.join(filePath, file);
                //scanPackageNameChild=true 如果某个文件夹名字需要扫描，则该文件下下面所有的子文件都需要扫描
                fileArray.push(...this.getFileByFilter(modulePath, fullPath, ignorePath))
            });

            return fileArray
        } else {
            return []
        }
    }

}


/** 
 * "name": "PageOne",
 *  "pageSourceFile": "src/main/ets/pages/PageOne.ets",
 * "buildFunction": "PageOneBuilder",
 * "data": {
 *   "interception" : "this is PageOne"
 * }
 */
class RouterInfo {
    name: string | undefined
    pageSourceFile: string | undefined
    buildFunction: string | undefined
    data: Data | undefined
}

class Data {
    interceptionTag: string | undefined
    interceptionTip: string | undefined
}

class RouterBuilderInfo {
    componentName: string | undefined
    pageSourceFile: string | undefined
    componentBuilder: string | undefined
}

class Analyzer {

    //路由注解名字
    private annotation: string
    //扫描到的ets文件路径
    private filePath: string
    private modulePath: string
    private projectPath: string

    routerInfo: RouterInfo | undefined

    routerBuilderInfo: RouterBuilderInfo | undefined

    nameNeedChange: boolean = false

    constructor(annotation: string, filePath: string, modulePath: string, projectPath: string) {
        this.annotation = annotation;
        this.filePath = filePath;
        this.modulePath = modulePath
        this.projectPath = projectPath
    }



    start() {
        const sourceCode = fs.readFileSync(this.filePath, 'utf-8');
        const sourceFile = ts.createSourceFile(this.filePath, sourceCode, ts.ScriptTarget.ES2021, false);
        // 遍历节点信息
        ts.forEachChild(sourceFile, (node: ts.Node) => {
            // 解析节点
            this.resolveNode(node, sourceFile);
        });
    }

    findComponetNode(pos: number, sourceFile: ts.SourceFile): ts.Node | undefined {
        let findNode: ts.Node | undefined = undefined
        ts.forEachChild(sourceFile, (node: ts.Node) => {
            // 解析节点
            if (node.pos == pos) {
                findNode = node
            }
        });
        return findNode
    }
    resolveNode(node: ts.Node, sourceFile: ts.SourceFile) {
        if (node.kind != ts.SyntaxKind.MissingDeclaration) {
            return false
        }

        //@Component+@自定义装饰器
        let child = node as ts.ParameterDeclaration
        let modifiers = child.modifiers
        //如果装饰器不为空且大于等于2
        if (modifiers && modifiers.length >= 2) {

            modifiers.forEach((node) => {
                let decorator = node as Decorator
                if (decorator && decorator.expression) {
                    let callExpression = decorator.expression as CallExpression
                    if (callExpression.kind == ts.SyntaxKind.CallExpression) {
                        //如果有注解和参数不为空
                        let arg = callExpression.arguments
                        if (callExpression.expression && arg && arg.length > 0) {
                            //如果注解名字匹配
                            if (this.annotation == (callExpression.expression as Identifier).escapedText) {
                                log("扫描到需要解析的HRouter-" + this.filePath);
                                let properties: ts.NodeArray<PropertyAssignment> = (arg[0] as ObjectLiteralExpression).properties as ts.NodeArray<PropertyAssignment>
                                if (properties && properties.length > 0) {
                                    let nameItem = properties.find(item => (item.name as Identifier).escapedText == "name")
                                    if (nameItem) {
                                        if (ts.isStringLiteral(nameItem.initializer) && !(nameItem.initializer as ts.StringLiteral).text.startsWith("/")) {
                                            log("error --------路由地址需要以/开头-" + (nameItem.initializer as ts.StringLiteral).text)
                                        } else {
                                            this.routerInfo = new RouterInfo()
                                            this.routerBuilderInfo = new RouterBuilderInfo()
                                            let componentNode = this.findComponetNode(child.end + 7, sourceFile) as ExpressionStatement
                                            let componentIdentifier = componentNode.expression as Identifier
                                            let componentName = componentIdentifier.escapedText!
                                            this.routerBuilderInfo.componentName = componentName
                                            properties.forEach((p) => {
                                                let identifier = p.name as Identifier
                                                switch (identifier.escapedText) {
                                                    case "name":
                                                        if (p.initializer.kind == 211) {
                                                            let name = p.initializer as ts.PropertyAccessExpression
                                                            let enumKey = name.name.escapedText!
                                                            let value = this.readEnumValue(enumKey)
                                                            this.routerInfo!.name = value
                                                            this.nameNeedChange = false
                                                        } else {
                                                            let path = (p.initializer as ts.StringLiteral).text
                                                            if (path) {
                                                                this.routerInfo!.name = path
                                                            }
                                                            this.nameNeedChange = true
                                                        }
                                                        log("nameNeedChange-" + this.nameNeedChange)
                                                        break;
                                                    case "interceptionTag":
                                                        let interception = (p.initializer as ts.StringLiteral).text
                                                        if (interception) {
                                                            if (!this.routerInfo!.data) {
                                                                this.routerInfo!.data = new Data()
                                                            }
                                                            this.routerInfo!.data.interceptionTag = interception
                                                        }
                                                        break;
                                                    case "interceptionTip":
                                                        let interception_tip = (p.initializer as ts.StringLiteral).text
                                                        if (interception_tip) {
                                                            if (!this.routerInfo!.data) {
                                                                this.routerInfo!.data = new Data()
                                                            }
                                                            this.routerInfo!.data.interceptionTip = interception_tip
                                                        }
                                                        break

                                                }
                                            })
                                            try {
                                                let componentBuilder = this.routerInfo.name!.substring(1, this.routerInfo.name!.length).replace("/", "_")
                                                this.routerInfo.pageSourceFile = 'src/main/ets/' + ROUTER_GENERATE + "/" + componentBuilder + ".ets"
                                                this.routerInfo.buildFunction = componentBuilder + "Builder"
                                                this.routerBuilderInfo.componentBuilder = componentBuilder
                                                this.routerBuilderInfo.pageSourceFile = this.filePath.replace(this.modulePath + "/src/main/ets", "..").replace(".ets", "")
                                            } catch {
                                                this.routerInfo = undefined
                                                this.routerBuilderInfo = undefined
                                            }
                                        }
                                    }
                                }

                            }
                        }
                    }
                }
            })
        }
    }

    readEnumValue(key: string) {
        log("扫描枚举值 需要key-" + key)
        const enumPath = this.projectPath + "/common_router/src/main/ets/RouterEnum.ets"
        const enumFileContent = fs.readFileSync(enumPath, 'utf-8');
        const enumKeyValues: { [key: string]: string } = {};
        const enumRegex = /export\s+enum\s+\w+\s*{([^}]*)}/gm;
        const keyValueRegex = /(\w+)\s*=\s*["']([^"']+)["']/g;

        let enumMatch;
        while ((enumMatch = enumRegex.exec(enumFileContent)) !== null) {
            let keyValueMatch;
            while ((keyValueMatch = keyValueRegex.exec(enumMatch[1])) !== null) {
                enumKeyValues[keyValueMatch[1]] = keyValueMatch[2];
            }
        }
        log("扫描枚举值-" + JSON.stringify(enumKeyValues))
        return enumKeyValues[key]
    }
}